/**
 * 
 */
package jocl.samples;

import static org.jocl.CL.CL_CONTEXT_PLATFORM;
import static org.jocl.CL.CL_DEVICE_NAME;
import static org.jocl.CL.CL_DEVICE_TYPE_ALL;
import static org.jocl.CL.CL_MEM_COPY_HOST_PTR;
import static org.jocl.CL.CL_MEM_READ_ONLY;
import static org.jocl.CL.CL_MEM_READ_WRITE;
import static org.jocl.CL.clBuildProgram;
import static org.jocl.CL.clCreateBuffer;
import static org.jocl.CL.clCreateCommandQueue;
import static org.jocl.CL.clCreateContext;
import static org.jocl.CL.clCreateKernel;
import static org.jocl.CL.clCreateProgramWithSource;
import static org.jocl.CL.clEnqueueNDRangeKernel;
import static org.jocl.CL.clGetDeviceIDs;
import static org.jocl.CL.clReleaseCommandQueue;
import static org.jocl.CL.clReleaseContext;
import static org.jocl.CL.clReleaseEvent;
import static org.jocl.CL.clReleaseKernel;
import static org.jocl.CL.clReleaseMemObject;
import static org.jocl.CL.clReleaseProgram;
import static org.jocl.CL.clSetKernelArg;
import static org.jocl.CL.clWaitForEvents;
import static org.jocl.CL.CL_PLATFORM_NAME;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_command_queue;
import org.jocl.cl_context;
import org.jocl.cl_context_properties;
import org.jocl.cl_device_id;
import org.jocl.cl_event;
import org.jocl.cl_kernel;
import org.jocl.cl_mem;
import org.jocl.cl_platform_id;
import org.jocl.cl_program;

import sw.utils.JOCLUtils;

/**
 * @author Duy Chung
 *
 */
public class PlatformComputation implements Runnable {

	private cl_platform_id platform;
//	final long deviceType = CL_DEVICE_TYPE_ALL;
	private int n;
	private String programSource;
	private float output[];
	private float input[];
	private int globalSize;
	private long deviceType;
	
	private List<Integer> sizes = new ArrayList<Integer>();
	
	public PlatformComputation(cl_platform_id platform, 
							  int n, 
							  String programSource,
							  int globalSize,
							  long deviceType) {
		this.platform = platform;
		this.n = n;
		this.programSource = programSource;
		this.globalSize = globalSize;
		this.deviceType = deviceType;
		input = new float[n];
		Arrays.fill(input, 1.234f);
	}
	
	public void run() {
		
		 // Initialize the context properties
        cl_context_properties contextProperties = new cl_context_properties();
        contextProperties.addProperty(CL_CONTEXT_PLATFORM, platform);
		
		// get number of devices
		int numDevicesArray[] = new int[1];
	    clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray);	   
		int numDevices = numDevicesArray[0];
		
		 // Obtain a device IDs 
        cl_device_id devices[] = new cl_device_id[numDevices];
        clGetDeviceIDs(platform, deviceType, numDevices, devices, null);
        
//        for (int i=0; i<numDevices; i++) {
//            String deviceName = JOCLUtils.getString(devices[i], CL_DEVICE_NAME);
//            System.out.println("Device "+i+" of "+numDevices+": "+deviceName);
//        }

        // Create a context for the devices
        cl_context context = clCreateContext(contextProperties, devices.length, devices, null, null, null);

        // Create and build the program and the kernel
        cl_program program = clCreateProgramWithSource(context, 1, new String[]{ programSource }, null, null);
        clBuildProgram(program, 0, null, null, null, null);
        cl_kernel kernel = clCreateKernel(program, "sampleKernel", null);

        // Allocate the memory objects for the input- and output data
        cl_mem inputMems[] = new cl_mem[numDevices];
        cl_mem outputMems[] = new cl_mem[numDevices];
        
        for (int i=0; i<numDevices; i++) {
            
            inputMems[i] = clCreateBuffer(context, 
            							CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, 
            							Sizeof.cl_float * n, 
            							Pointer.to(input), null);
            
            outputMems[i] = clCreateBuffer(context, 
            							   CL_MEM_READ_WRITE, 
            							   Sizeof.cl_float * n,
            							   null, null);
        }

        // Create one command-queue for each device
        cl_command_queue commandQueues[] = new cl_command_queue[numDevices];
        long properties = 0;
        properties |= CL.CL_QUEUE_PROFILING_ENABLE;
        for (int i=0; i<numDevices; i++) {
            commandQueues[i] = clCreateCommandQueue(context, devices[i], properties, null);
        }
        
        // Execute the kernel on each command queue, and 
        // create events for each kernel launch
        cl_event events[] = new cl_event[numDevices];
        
        for (int i=0; i<numDevices; i++) {
            clSetKernelArg(kernel, 0, Sizeof.cl_mem, Pointer.to(inputMems[i]));
            clSetKernelArg(kernel, 1, Sizeof.cl_mem, Pointer.to(outputMems[i]));
            clSetKernelArg(kernel, 2, Sizeof.cl_int, Pointer.to(new int[]{this.n}));
            
            events[i] = new cl_event();

//            String name = JOCLUtils.getString(devices[i], CL.CL_DEVICE_NAME);
//            long size = 0;
//            if ( name.contains("Intel(R) HD Graphics 4000")) {
//            	size = 9000;
//            } else {
//            	size = 85500;
//            } 
            
            clEnqueueNDRangeKernel(commandQueues[i], 
            					  kernel, 
            					  1, 
            					  null, 
            					  new long[]{n / numDevices}, // global work size
            					  null, 
            					  0, 
            					  null, 
            					  events[i]);
            
        }
        
        // Wait until the work is finished on all command queues
        clWaitForEvents(events.length, events);
        for (int i=0; i<numDevices; i++)
        {
            float durationMs = JOCLUtils.computeDurationMs(events[i]);
            System.out.println("Duration on device "+ JOCLUtils.getString(devices[i], CL_DEVICE_NAME) + ": " + durationMs+"ms");
        }

        // Read the output data
//        for (int i = 0 ; i < numDevices; i++) {
//            clEnqueueReadBuffer(commandQueues[i], outputMems[i], CL_TRUE, 0,
//                    n/numDevices * Sizeof.cl_float, Pointer.to(output[i]), 0, null, null);
//        }

//        clEnqueueReadBuffer(commandQueues[0], outputMems[0], CL_TRUE, 0,
//                (n) * Sizeof.cl_float, Pointer.to(output1), 0, null, null);
        
        
//         print the values in first device
//        for (int k = 0; k < (n/2); k++) {        	        	
//        	
//        	if (output1[k] < 56111.62f) {
//        		System.out.println("FAILED on device1");
//        	}
//        }
//        System.out.println("PASSED on device1");
        
        
        // Read the output data of the second device
//        clEnqueueReadBuffer(commandQueues[1], outputMems[1], CL_TRUE, 0,
//                (n) * Sizeof.cl_float, Pointer.to(output2), 0, null, null);
        
//         print the values in first device
//        for (int k = 0; k < (n/2); k++) {
//        	if (output2[k] < 56111.62f) {
//        		System.out.println("FAILED on device2");
//        	}
//        }
//        System.out.println("PASSED on device2");
        
        
        // Release kernel, program, and memory objects
        clReleaseKernel(kernel);
        clReleaseProgram(program);
        for (int i=0; i<numDevices; i++) {
            clReleaseMemObject(inputMems[i]);
            clReleaseMemObject(outputMems[i]);
            clReleaseEvent(events[i]);
            clReleaseCommandQueue(commandQueues[i]);
        }
        clReleaseContext(context);
	}
	
	public void addSize(int size) {
		sizes.add(size);
	}
}
